package Try.Proxy.MineProxy;

import Try.Proxy.Subject;
import Try.Proxy.SubjectImpl;

import javax.tools.JavaCompiler;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.concurrent.atomic.AtomicInteger;

public class MyProxy {
    private static String proxyName = "$Proxy";
    private static AtomicInteger number = new AtomicInteger(0);


    /**
     * 生成代理对象
     * @param loader 类加载器，用于加载被代理类的类文件
     * @param interfaces 被代理类的接口
     * @param h 自定义的InvocationHandler接口,用于具体代理方法的执行
     * @return 返回被代理后的代理对象
     * @throws IllegalArgumentException
     */
    public static Object newProxyInstance(MyClassLoader loader,
                                          Class<?>[] interfaces,
                                          MyInvocationHandler h)
            throws IllegalArgumentException, IOException {
        /**
         * 1.生成代理类的源代码
         * 2.将生成的源代码输出到磁盘，保存为.java文件
         * 3.编译源代码，并生成.java文件
         * 4.将class文件中的内容，动态加载到JVM中
         * 5.返回被代理后的代理对象
         */

        //1.生成源代码
        ProxyInfo proxyInfo = getClassResource(interfaces);

        //2.把代码写入到.java文件中保存在磁盘
        String path = MyProxy.class.getResource("").getPath();
        File file = new File(path + proxyInfo.getName() + ".java");
        try {
            FileWriter writer = new FileWriter(file);
            writer.write(proxyInfo.getRes());
            writer.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 3.编译源代码，并生成.java文件
        // 获取java编译器
        JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
        // 标注java文件管理器，用来获取java字节码文件
        StandardJavaFileManager manager = javaCompiler.getStandardFileManager(null,null,null);
        Iterable iterable = manager.getJavaFileObjects(file);

        // 创建task，通过java字节码文件将类信息加载到JVM中
        JavaCompiler.CompilationTask task = javaCompiler.getTask(null,manager,null,null,null,iterable);
        // 开始执行task
        task.call();
        // 关闭管理器
        manager.close();

        return null;

    }


    /**
     * 构造class文件资源
     * @param interfaces
     * @return
     */
    public static ProxyInfo getClassResource(Class<?>[] interfaces){

        StringBuilder csBuilder = new StringBuilder();
        int num = number.getAndIncrement();
        csBuilder.append("package").append(" Try.Proxy.MineProxy;").append("\n")
        .append("import java.lang.reflect.Method;").append("\n")
        .append("class " + proxyName + num +" implements");

        if(interfaces.length>0){
            csBuilder.append(" "+interfaces[0].getName());
        }

        if (interfaces.length>1){
            for (int i=0;i<interfaces.length;i++){
                csBuilder.append(","+interfaces[i].getName());
            }
        }

        csBuilder.append("{").append("\n")
        .append("private MyInvocationHandler h;").append("\n")
        .append(proxyName + num +"(MyInvocationHandler handler) {h=handler;}").append("\n")
        ;

        for(Class<?> interf : interfaces){
            Method[] methods = interf.getMethods();
            for (Method method : methods){
                csBuilder.append("public " + method.getReturnType() + " " + method.getName() + "(){").append("\n")
                        .append("try{").append("\n")
                        .append("Method m = ").append(interf.getName()).append(".class.getMethod(\"").append(method.getName()).append("\");").append("\n")
                        .append("h.invoke(this,m,new Object[]{null});").append("\n")
                .append("}")
                .append("catch(Throwable e){").append("\n")
                .append("e.printStackTrace();").append("\n")
                .append("}").append("\n")
                .append("}\n")
                .append("}");
            }
        }

        ProxyInfo proxyInfo = new ProxyInfo(csBuilder.toString(), proxyName+num);

        return proxyInfo;
    }


    public static void main(String[] args) throws IOException {
        Subject subject = new SubjectImpl();
        MyProxy.newProxyInstance(null,subject.getClass().getInterfaces(),null);
    }
}